package cn.edu.buaa.sei.jdat;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.HashSet;
import java.util.List;
import java.util.StringTokenizer;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.ConstantClass;
import org.apache.bcel.classfile.ConstantString;
import org.apache.bcel.classfile.ConstantUtf8;
import org.apache.bcel.classfile.DescendingVisitor;
import org.apache.bcel.classfile.EmptyVisitor;
import org.apache.bcel.classfile.JavaClass;

import cn.edu.buaa.sei.jdat.graph.DependencyGraph;
import cn.edu.buaa.sei.jdat.model.Field;
import cn.edu.buaa.sei.jdat.model.Interface;
import cn.edu.buaa.sei.jdat.model.Jar;
import cn.edu.buaa.sei.jdat.model.Method;
import cn.edu.buaa.sei.jdat.model.Package;
import cn.edu.buaa.sei.jdat.model.Type;
import cn.edu.buaa.sei.jdat.model.Class;

//
//
//  Generated by StarUML(tm) Java Add-In
//
//  @ Project : Jar Dependency Analysis Toolkit
//  @ File Name : JarController.java
//  @ Date : 2012/1/13
//  @ Author : 
//
//




public class JarController {
	private final JarCollection collection;
	private final List<StateChangeListener> listeners;
	private final UnresolvedTable unresolvedTable;
	private final ResolvedTable resolvedTable;
	
	public static class UnresolvedTable {
		
		private Hashtable<String, ArrayList<Type>> table = new Hashtable<String, ArrayList<Type>>();
		
		public void addItem(String refName, Type type) {
			if (table.containsKey(refName)) {
				table.get(refName).add(type);
			} else {
				ArrayList<Type> array = new ArrayList<Type>();
				array.add(type);
				table.put(refName, array);
			}
		}
		
		public void removeName(String refName) {
			table.remove(refName);
		}
		
		public List<Type> getTypes(String refName) {
			return table.get(refName);
		}
	}
	
	public static class ResolvedTable {
		private Hashtable<String, ArrayList<Type>> table = new Hashtable<String, ArrayList<Type>>();
		
		public void addType(Type type) {
			String name = type.getSignature();
			if (table.containsKey(name)) {
				table.get(name).add(type);
			} else {
				ArrayList<Type> array = new ArrayList<Type>();
				array.add(type);
				table.put(name, array);
			}
		}
		
		public void removeType(Type type) {
			String name = type.getSignature();
			if (table.containsKey(name)) {
				ArrayList<Type> array = table.get(name);
				array.remove(type);
				if (array.size() == 0)
					table.remove(name);
			}
		}
		
		public Type getType(String name) {
			return table.containsKey(name) ? table.get(name).get(0) : null;
		}
	}
	
	public JarController() {
		collection = new JarCollection();
		listeners = new ArrayList<JarController.StateChangeListener>();
		unresolvedTable = new UnresolvedTable();
		resolvedTable = new ResolvedTable();
	}
	
	public void importJar(String url) {
	
	}
	
	public void removeJar(Jar jar) {
	
	}
	
	public Jar getJar(Integer index) {
		// TODO
		return null;
	}
	
	public Jar[] getJars(int[] indices) {
		// TODO
		return null;
	}
	
	public DependencyGraph generateGraph(Jar[] jars) {
		// TODO
		return null;
	}
	
	public int getJarCount() {
		// TODO
		return 0;
	}
	
	private void buildStructure(Jar jar) {
	
	}
	
	private void appendDependency(Jar jar) {
	
	}
	
	private void removeDependency(Jar jar) {
	
	}
	
	public static interface StateChangeListener {
		void stateChange(Jar jar, Integer state);
	}
	
	// helper methods
	
	private void buildStructureFromFile(Jar jar, File file, String url_id) throws IOException {
		JarFile jarFile = new JarFile(file);
		
		Enumeration<JarEntry> it = jarFile.entries();
		while (it.hasMoreElements()) {
			
			// enumerate classes in jar
			JarEntry jarEntry = it.nextElement();
			if (jarEntry.getName().endsWith(".class")) {
				
				// parse class
				InputStream is = jarFile.getInputStream(jarEntry);
				ClassParser parser = new ClassParser(is, jarEntry.getName());
				JavaClass javaClass = parser.parse();
				
				// create owner package
				String packageName = javaClass.getPackageName();
				Package p = jar.getPackage(packageName);
				if (p == null) {
					p = new Package();
					p.setName(packageName);
					jar.addPackage(p);
				}
				
				// create type
				Type type = javaClass.isInterface() ? new Interface() : new Class();
				p.addType(type); // add type to package
				type.setOwner(p);
				String name = javaClass.getClassName();
				if (name.contains(".")) {
					name = name.substring(name.lastIndexOf('.') + 1);
				}
				type.setName(name);
				org.apache.bcel.classfile.Field[] fields = javaClass.getFields();
				org.apache.bcel.classfile.Method[] methods = javaClass.getMethods();
				
				// add field to type
				for (org.apache.bcel.classfile.Field field : fields) {
					Field f = new Field();
					f.setName(field.getName());
					f.setType(Type.createUnresolvedType(field.getType().toString()));
					f.setOwner(type);
				}
				
				// add methods to type
				for (org.apache.bcel.classfile.Method method : methods) {
					Method m = new Method();
					m.setName(method.getName());
					m.setReturnType(Type.createUnresolvedType(method.getReturnType().toString()));
					for (org.apache.bcel.generic.Type at : method.getArgumentTypes()) {
						m.addArgument(Type.createUnresolvedType(at.toString()));
					}
					m.setOwner(type);
				}
				
				// set class property
				if (type instanceof Class) {
					((Class) type).setAbstract(javaClass.isAbstract());
					((Class) type).setStatic(javaClass.isStatic());
					((Class) type).setFinal(javaClass.isFinal());
				}
				
				// get all references of this type
				String[] refs = parseReference(javaClass);
				for (String ref : refs) {
					Type t = resolvedTable.getType(ref);
					if (t != null) {
						t.addInverseReference(type);
						type.addReference(t);
					} else {
						// update UnresolvedTable
						unresolvedTable.addItem(ref, type);
					}
				}

				// update ResolvedTable
				resolvedTable.addType(type);
				
				// update UnresolvedTable
				List<Type> unresolvedRefType = unresolvedTable.getTypes(type.getSignature());
				if (unresolvedRefType != null) {
					for (Type t : unresolvedRefType) {
						t.addReference(type);
						type.addInverseReference(t);
					};
					unresolvedTable.removeName(type.getSignature());
				}
			}
			
		}
	}
	
	private String[] parseReference(final JavaClass javaClass) {
		class DependencyVisitor extends EmptyVisitor {
			
			final public ArrayList<String> strings = new ArrayList<String>();
			final public HashSet<String> reference = new HashSet<String>();
			
			public void visitConstantClass(ConstantClass cls) {
				String sCls = cls.getBytes(javaClass.getConstantPool());
				if (sCls.indexOf("/") != -1) {
					sCls = sCls.replace('/', '.');
					sCls = this.cleanClass(sCls);
					if (!reference.contains(sCls) && !sCls.equals(javaClass.getClassName())) {
						reference.add(sCls);
					}
				}
			}

			public void visitConstantUtf8(ConstantUtf8 utf) {
				String utfString = utf.toString().substring(utf.toString().indexOf('"') + 1,utf.toString().lastIndexOf('"'));
				if (isValidJavaClass(utfString)) {
					if (!this.strings.contains(utfString)) {
						String[] classes = this.separateClasses(utfString);
						for (int i = 0; i < classes.length; i++) {
							if (classes[i] != null) {
								String cls = classes[i];
								// String packageName = this.stripClassName(cls);
								cls = cls.replace('/', '.');
								cls = this.cleanClass(cls);
								if (!reference.contains(cls) && !cls.equals(javaClass.getClassName())) {
									reference.add(cls);
								}
							}
						}
					}
				}

			}

			public void visitConstantString(ConstantString str) {
				this.strings.add(str.getBytes(javaClass.getConstantPool()).toString());
			}
			
			private boolean isValidJavaClass(String cls) {
				if (cls.indexOf("/") == -1) {
					return false;
				}
				if ( (!cls.startsWith("(")) && (!cls.startsWith("L")) ) {
					return false;
				}

				if ( (!cls.endsWith("V")) && (!cls.endsWith(";")) && (!cls.endsWith(")")) ) {
					return false;
				}

				return true;
			}

			private String[] separateClasses(String utfString) {
				StringTokenizer tokenizer = new StringTokenizer(utfString, ";");
				String classes[] = new String[tokenizer.countTokens()];
				int i = 0;
				while (tokenizer.hasMoreTokens()) {
					String cls = tokenizer.nextToken();
					if (cls.indexOf('/') != -1) {
						classes[i] = cls;
						i++;
					}
				}
				return classes;
			}

			private String cleanClass(String cls) {
				if (cls.startsWith("L")) return cls.substring(1);
				else if (cls.contains(".")) { // not in default package
					int index = cls.substring(0, cls.lastIndexOf(".")).lastIndexOf('L');
					if (index != -1) {
						return cls.substring(index + 1);
					}
				}
				return cls;
			}
			
		}
		
		DependencyVisitor refVisitor = new DependencyVisitor();
		DescendingVisitor descendingVisitor = new DescendingVisitor(javaClass, refVisitor);
		javaClass.accept(descendingVisitor);
		return refVisitor.reference.toArray(new String[0]);
	}
	
	public static void main(String[] args) {
		JarController c = new JarController();
		Jar jar = new Jar("url");
		try {
			c.buildStructureFromFile(jar, new File("lib/bcel-5.2.jar"), null);
			jar.toString();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
}
